package com.cardone.platform.configuration.util;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;

import com.cardone.common.Characters;
import com.cardone.common.cache.util.CacheUtils;
import com.cardone.common.dto.ReturnDataDto;
import com.cardone.common.template.util.TemplateUtils;
import com.cardone.common.util.MapperUtils;
import com.cardone.common.util.ReturnDataUtils;
import com.cardone.context.Attributes;
import com.cardone.context.ContextHolder;
import com.cardone.context.Contexts;
import com.cardone.context.DictionaryException;
import com.cardone.context.function.Execution0Function;
import com.cardone.platform.authority.util.PermissionUtils;
import com.cardone.platform.common.service.CommonService;
import com.cardone.platform.common.util.CommonUtils;
import com.cardone.platform.configuration.dto.SiteDto;
import com.cardone.platform.configuration.service.SiteService;
import com.cardone.platform.usercenter.dto.UserDto;
import com.cardone.platform.usercenter.util.UserUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * 站工具类
 *
 * @author yaohaitao
 *
 */
@Slf4j
public class SiteUtils {
	/**
	 * 数据位置
	 */
	@Setter
	@Getter
	private static Resource dataLocation;

	/**
	 * 数据路径扩展名
	 */
	@Setter
	private static String dataUrlExtension = ".json";

	/**
	 * 默认主页视图
	 */
	@Setter
	@Getter
	private static String defaultIndexView;

	/**
	 * 默认站
	 */
	@Setter
	private static SiteDto defaultSite;

	private static boolean init;

	/**
	 * json路径集合
	 */
	@Setter
	private static List<String> jsonUrlList = Lists.newArrayList("/**/*.json");

	/**
	 * 不缓存的文件
	 */
	@Setter
	private static List<String> noCacheUrlList = Lists.newArrayList("/**/*.html");

	/**
	 * 简单授权
	 */
	@Setter
	private static boolean simpleAuthority = true;

	/**
	 * 上传临时目录
	 */
	@Setter
	@Getter
	private static Resource uploadTempDir;

	/**
	 * urlCodec
	 */
	@Setter
	private static URLCodec urlCodec = new URLCodec();

	/**
	 * 视图位置
	 */
	@Setter
	@Getter
	private static Resource viewLocation;

	/**
	 * 视图扩展名
	 */
	@Setter
	private static String viewNameExtension = ".html";

	/**
	 * 视图路径404
	 */
	@Setter
	private static String viewNameFor404 = "error/404";

	/**
	 * Callback after completion of request processing, that is, after rendering
	 * the view. Will be called on any outcome of handler execution, thus allows
	 * for proper resource cleanup.
	 * <p>
	 * Note: Will only be called if this interceptor's {@code preHandle} method
	 * has successfully completed and returned {@code true}!
	 * <p>
	 * As with the {@code postHandle} method, the method will be invoked on each
	 * interceptor in the chain in reverse order, so the first interceptor will be
	 * the last to be invoked.
	 *
	 * @param request
	 *          current HTTP request
	 * @param response
	 *          current HTTP response
	 * @param handler
	 *          handler (or {@link HandlerMethod}) that started async execution,
	 *          for type and/or instance examination
	 * @param ex
	 *          exception thrown on handler execution, if any
	 * @throws Exception
	 *           in case of errors
	 */
	public static void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception {
		final String url = request.getServletPath();

		if (com.cardone.common.util.StringUtils.matchList(SiteUtils.noCacheUrlList, url)) {
			response.setHeader("Pragma", "no-cache");
			response.addHeader("Cache-Control", "must-revalidate");
			response.addHeader("Cache-Control", "no-cache");
			response.addHeader("Cache-Control", "no-store");
			response.setDateHeader("Expires", 0);
		}
	}

	/**
	 * Called instead of {@code postHandle} and {@code afterCompletion}, when the
	 * a handler is being executed concurrently. Implementations may use the
	 * provided request and response but should avoid modifying them in ways that
	 * would conflict with the concurrent execution of the handler. A typical use
	 * of this method would be to clean thread local variables.
	 *
	 * @param request
	 *          the current request
	 * @param response
	 *          the current response
	 * @param handler
	 *          handler (or {@link HandlerMethod}) that started async execution,
	 *          for type and/or instance examination
	 * @throws Exception
	 *           in case of errors
	 */
	public static void afterConcurrentHandlingStarted(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
	}

	/**
	 * 填充数据
	 *
	 * @param request
	 */
	public static void fillData(final HttpServletRequest request) {
		final List<String> proxyFunctionNameList = CommonUtils.readListProxyFunctionName();

		final String servletPath = request.getServletPath();

		if (!CollectionUtils.isEmpty(proxyFunctionNameList)) {
			final String proxyFunctionName = com.cardone.common.util.StringUtils.getPathForMatch(proxyFunctionNameList, servletPath);

			if (StringUtils.isNotBlank(proxyFunctionName)) {
				String parameters = com.cardone.common.util.WebUtils.getJsonString(request);

				final Object data = ContextHolder.getBean(CommonService.class).proxyFunction(UserUtils.readIdForLogin(), proxyFunctionName, parameters);

				if (data == null) {
					return;
				}

				if (data instanceof Map) {
					@SuppressWarnings("unchecked")
					Map<String, Object> model = (Map<String, Object>) data;

					if (CollectionUtils.isEmpty(model)) {
						return;
					}

					WebUtils.exposeRequestAttributes(request, model);

					return;
				}

				request.setAttribute(Contexts.data.name(), data);

				return;
			}
		}

		if (SiteUtils.dataLocation == null) {
			return;
		}

		try {
			final SiteDto contextSite = SiteUtils.getContextSite(request);

			final String filename = SiteUtils.dataLocation.getFile().getAbsolutePath() + File.separator + contextSite.getStyleCode() + servletPath + SiteUtils.dataUrlExtension;

			final File jsonFile = FileUtils.getFile(filename);

			if (jsonFile == null) {
				return;
			}

			if (!jsonFile.exists()) {
				return;
			}

			final String jsonString = FileUtils.readFileToString(jsonFile);

			if (StringUtils.isBlank(jsonString)) {
				return;
			}

			final Map<String, Object> model = MapperUtils.getMapForJson(jsonString);

			if (CollectionUtils.isEmpty(model)) {
				return;
			}

			WebUtils.exposeRequestAttributes(request, model);
		} catch (final IOException e) {
			SiteUtils.log.error(e.getMessage(), e);
		}
	}

	/**
	 * 查询
	 *
	 * @param mappedClass
	 *          返回类型
	 *
	 * @param code
	 *          代码
	 *
	 * @return 返回对象
	 */
	public static <P> P findByCode(final Class<P> mappedClass, final String code) {
		return ContextHolder.getBean(SiteService.class).findByCode(mappedClass, code);
	}

	/**
	 * 查询：站
	 *
	 * @param code
	 *          代码
	 * @return 站
	 */
	public static SiteDto findByCode(final String code) {
		return ContextHolder.getBean(SiteService.class).findByCode(SiteDto.class, code);
	}

	/**
	 * 查询
	 *
	 * @param mappedClass
	 *          返回类型
	 *
	 * @param siteUrlCode
	 *          站路径代码
	 *
	 * @return 返回对象
	 */
	public static <P> P findBySiteUrlCode(final Class<P> mappedClass, final String siteUrlCode) {
		return ContextHolder.getBean(SiteService.class).findBySiteUrlCode(mappedClass, siteUrlCode);
	}

	/**
	 * 查询：站
	 *
	 * @param siteUrlCode
	 *          站路径代码
	 * @return 站
	 */
	public static SiteDto findBySiteUrlCode(final String siteUrlCode) {
		return ContextHolder.getBean(SiteService.class).findBySiteUrlCode(SiteDto.class, siteUrlCode);
	}

	/**
	 * 查询
	 *
	 * @param mappedClass
	 *          返回类型
	 *
	 * @param projectCode
	 *          项目代码
	 *
	 * @return 站对象集合
	 */
	public static <P> List<P> findListByProjectCode(final Class<P> mappedClass, final String projectCode) {
		return ContextHolder.getBean(SiteService.class).findListByProjectCode(mappedClass, projectCode);
	}

	/**
	 * 查询:站
	 *
	 * @param projectCode
	 *          项目代码
	 *
	 * @return 站对象集合
	 */
	public static List<SiteDto> findListByProjectCode(final String projectCode) {
		return ContextHolder.getBean(SiteService.class).findListByProjectCode(SiteDto.class, projectCode);
	}

	public static ReturnDataDto<Object> fromJsonForReturnData(final HttpServletRequest request) throws IOException {
		final List<String> proxyFunctionNameList = CommonUtils.readListProxyFunctionName();

		final String servletPath = request.getServletPath();

		if (!CollectionUtils.isEmpty(proxyFunctionNameList)) {
			final String proxyFunctionName = com.cardone.common.util.StringUtils.getPathForMatch(proxyFunctionNameList, servletPath);

			if (StringUtils.isNotBlank(proxyFunctionName)) {
				String parameters = com.cardone.common.util.WebUtils.getJsonString(request);

				final Object data = ContextHolder.getBean(CommonService.class).proxyFunction(UserUtils.readIdForLogin(), proxyFunctionName, parameters);

				return ReturnDataUtils.newReturnData(data);
			}
		}

		if (SiteUtils.dataLocation == null) {
			throw new DictionaryException("本地数据路径 dataLocation 未设置").setCode("404");
		}

		final SiteDto contextSite = SiteUtils.getContextSite(request);

		final String filename = SiteUtils.dataLocation.getFile().getAbsolutePath() + File.separator + contextSite.getStyleCode() + servletPath;

		final File jsonFile = FileUtils.getFile(filename);

		if ((jsonFile == null) || !jsonFile.exists()) {
			throw new DictionaryException("本地数据路径 不存在").setCode("404");
		}

		final String jsonString = FileUtils.readFileToString(jsonFile);

		final ReturnDataDto<Object> returnData = new ReturnDataDto<Object>();

		if (StringUtils.isBlank(jsonString)) {
			return returnData;
		}

		final Map<String, Object> returnDataMap = MapperUtils.getMapForJson(jsonString);

		returnData.setCode(MapUtils.getInteger(returnDataMap, Attributes.code.name()));
		returnData.setTypeCode(MapUtils.getString(returnDataMap, Attributes.typeCode.name()));
		returnData.setMessage(MapUtils.getString(returnDataMap, Contexts.message.name()));
		returnData.setData(MapUtils.getObject(returnDataMap, Contexts.data.name()));

		return ReturnDataUtils.newReturnData(returnData);
	}

	/**
	 * 获取上下文参数
	 *
	 * @return 上下文参数
	 */
	public static SiteDto getContextSite() {
		final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

		return SiteUtils.getContextSite(request);
	}

	/**
	 * 获取上下文参数
	 *
	 * @param request
	 *          request
	 * @return 上下文参数
	 */
	public static SiteDto getContextSite(final HttpServletRequest request) {
		SiteDto contextSite = (SiteDto) request.getAttribute(Contexts.contextSite.name());

		if (contextSite != null) {
			return contextSite;
		}

		try {
			final String[] key = { Contexts.contextSite.name(), request.getServerName(), request.getContextPath() };

			contextSite = CacheUtils.getCacheValue(SiteUtils.class.getName(), key, new Execution0Function<SiteDto>() {
				@Override
				public SiteDto execution() {
					return ContextHolder.getBean(SiteService.class).findByServerName(SiteDto.class, request.getServerName(), SiteUtils.defaultSite.getProjectCode(), SiteUtils.defaultSite.getCode());
				}
			});
		} catch (final Exception e) {
			SiteUtils.log.error(e.getMessage(), e);
		}

		if (contextSite == null) {
			contextSite = new SiteDto();
		}

		SiteUtils.initSite(request, contextSite);

		request.setAttribute(Contexts.contextSite.name(), contextSite);

		return contextSite;
	}

	/**
	 * 初始化
	 *
	 * @param request
	 *          站
	 */
	private static void init(final HttpServletRequest request) {
		if (SiteUtils.init) {
			return;
		}

		if (SiteUtils.defaultSite == null) {
			SiteUtils.defaultSite = new SiteDto();
		}

		if (StringUtils.isNotBlank(SiteUtils.defaultSite.getLoginUrl())) {
			final Map<String, Object> model = Maps.newHashMap();

			model.put(Contexts.contextPath.name(), request.getContextPath());

			final String loginUrl = TemplateUtils.processString(SiteUtils.defaultSite.getLoginUrl(), model);

			SiteUtils.defaultSite.setLoginUrl(loginUrl);
		}

		if (StringUtils.isNotBlank(SiteUtils.defaultSite.getNotAuthorityUrl())) {
			final Map<String, Object> model = Maps.newHashMap();

			model.put(Contexts.contextPath.name(), request.getContextPath());

			final String notAuthorityUrl = TemplateUtils.processString(SiteUtils.defaultSite.getNotAuthorityUrl(), model);

			SiteUtils.defaultSite.setNotAuthorityUrl(notAuthorityUrl);
		}

		final List<String> urlBlackList = SiteUtils.defaultSite.getUrlBlackList();

		com.cardone.common.util.CollectionUtils.sortForLength(urlBlackList);

		SiteUtils.defaultSite.setUrlBlackList(urlBlackList);

		final List<String> urlWhiteList = SiteUtils.defaultSite.getUrlWhiteList();

		com.cardone.common.util.CollectionUtils.sortForLength(urlWhiteList);

		SiteUtils.defaultSite.setUrlWhiteList(urlWhiteList);

		com.cardone.common.util.CollectionUtils.sortForLength(SiteUtils.jsonUrlList);

		com.cardone.common.util.CollectionUtils.sortForLength(SiteUtils.noCacheUrlList);

		SiteUtils.init = true;
	}

	/**
	 * 初始化站
	 *
	 * @param request
	 *          HttpServletRequest
	 * @param site
	 *          站对象
	 */
	private static void initSite(final HttpServletRequest request, final SiteDto site) {
		SiteUtils.init(request);

		if (CollectionUtils.isEmpty(site.getUrlWhiteList())) {
			site.setUrlWhiteList(SiteUtils.defaultSite.getUrlWhiteList());
		}

		if (CollectionUtils.isEmpty(site.getUrlBlackList())) {
			site.setUrlBlackList(SiteUtils.defaultSite.getUrlBlackList());
		}

		if (StringUtils.isBlank(site.getLoginUrl())) {
			site.setLoginUrl(SiteUtils.defaultSite.getLoginUrl());
		}

		if (StringUtils.isBlank(site.getNotAuthorityUrl())) {
			site.setNotAuthorityUrl(SiteUtils.defaultSite.getNotAuthorityUrl());
		}

		if (StringUtils.isBlank(site.getStyleCode())) {
			site.setStyleCode(SiteUtils.defaultSite.getStyleCode());
		}

		site.setContextPath(request.getContextPath()).setRequestUri(request.getRequestURI()).setServletPath(request.getServletPath());

		site.setServerName(request.getServerName());
	}

	/**
	 * 视图是否存在
	 *
	 * @param viewName
	 *          视图名称
	 * @return 是否存在
	 */
	private static boolean isViewExists(final String viewName) {
		if (SiteUtils.viewLocation == null) {
			return true;
		}

		try {
			final String filename = SiteUtils.viewLocation.getFile().getAbsolutePath() + File.separator + viewName + SiteUtils.viewNameExtension;

			final File viewFile = FileUtils.getFile(filename);

			if (viewFile == null) {
				return false;
			}

			return viewFile.exists();
		} catch (final IOException e) {
			SiteUtils.log.error(e.getMessage(), e);
		}

		return true;
	}

	/**
	 * Intercept the execution of a handler. Called after HandlerAdapter actually
	 * invoked the handler, but before the DispatcherServlet renders the view. Can
	 * expose additional model objects to the view via the given ModelAndView.
	 * <p>
	 * DispatcherServlet processes a handler in an execution chain, consisting of
	 * any number of interceptors, with the handler itself at the end. With this
	 * method, each interceptor can post-process an execution, getting applied in
	 * inverse order of the execution chain.
	 *
	 * @param request
	 *          current HTTP request
	 * @param response
	 *          current HTTP response
	 * @param handler
	 *          handler (or {@link HandlerMethod}) that started async execution,
	 *          for type and/or instance examination
	 * @param modelAndView
	 *          the {@code ModelAndView} that the handler returned (can also be
	 *          {@code null})
	 * @throws Exception
	 *           in case of errors
	 */
	public static void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception {
		if (modelAndView == null) {
			return;
		}

		String viewName = modelAndView.getViewName();

		SiteUtils.log.debug(viewName);

		if (StringUtils.isNotBlank(viewName)) {
			if (StringUtils.contains(viewName, ":")) {
				return;
			}

			if (!StringUtils.startsWith(viewName, Characters.httpSeparator.stringValue())) {
				viewName = Characters.httpSeparator.stringValue() + viewName;
			}

			final SiteDto contextSite = SiteUtils.getContextSite(request);

			viewName = contextSite.getStyleCode() + viewName;

			if (!SiteUtils.isViewExists(viewName)) {
				if (!StringUtils.startsWith(SiteUtils.viewNameFor404, Characters.httpSeparator.stringValue())) {
					viewName = Characters.httpSeparator.stringValue() + SiteUtils.viewNameFor404;
				}

				viewName = contextSite.getStyleCode() + viewName;
			}

			modelAndView.setViewName(viewName);

			modelAndView.addObject(Contexts.cardoneViewName.name(), viewName);
		}
	}

	/**
	 * Intercept the execution of a handler. Called after HandlerMapping
	 * determined an appropriate handler object, but before HandlerAdapter invokes
	 * the handler.
	 * <p>
	 * DispatcherServlet processes a handler in an execution chain, consisting of
	 * any number of interceptors, with the handler itself at the end. With this
	 * method, each interceptor can decide to abort the execution chain, typically
	 * sending a HTTP error or writing a custom response.
	 *
	 * @param request
	 *          current HTTP request
	 * @param response
	 *          current HTTP response
	 * @param handler
	 *          chosen handler to execute, for type and/or instance evaluation
	 * @return {@code true} if the execution chain should proceed with the next
	 *         interceptor or the handler itself. Else, DispatcherServlet assumes
	 *         that this interceptor has already dealt with the response itself.
	 * @throws Exception
	 *           in case of errors
	 */
	public static boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
		final SiteDto contextSite = SiteUtils.getContextSite(request);

		if (CollectionUtils.isEmpty(contextSite.getUrlWhiteList())) {
			return true;
		}

		final String url = request.getServletPath();

		if (com.cardone.common.util.StringUtils.matchList(contextSite.getUrlWhiteList(), url)) {
			return true;
		}

		final String permissionCode = com.cardone.common.util.StringUtils.getPathForMatch(contextSite.getUrlBlackList(), url);

		if (StringUtils.isBlank(permissionCode)) {
			return true;
		}

		final String loginKey = UserUtils.readLoginKey();

		if (StringUtils.isBlank(loginKey)) {
			if (com.cardone.common.util.StringUtils.matchList(SiteUtils.jsonUrlList, url)) {
				response.setCharacterEncoding(CharEncoding.UTF_8);
				response.setContentType("application/json");

				Writer out = null;

				try {
					final ReturnDataDto<String> returnDataDto = ReturnDataUtils.newReturnData(new DictionaryException("请登录系统").setTypeCode("login").setCode("not"), SiteUtils.log);

					out = response.getWriter();

					out.write(MapperUtils.toJson(returnDataDto));
				} catch (final Exception e) {
					SiteUtils.log.error(e.getMessage(), e);
				} finally {
					IOUtils.closeQuietly(out);
				}
			} else {
				String logingTargetUrl = null;

				do {
					logingTargetUrl = request.getHeader("logingTargetUrl");

					if (StringUtils.isNotBlank(logingTargetUrl)) {
						break;
					}

					logingTargetUrl = request.getParameter("logingTargetUrl");

					if (StringUtils.isNotBlank(logingTargetUrl)) {
						break;
					}

					logingTargetUrl = request.getRequestURI();

					final String queryString = request.getQueryString();

					if (StringUtils.isNotBlank(queryString)) {
						logingTargetUrl += "?" + queryString;
					}

					logingTargetUrl = SiteUtils.urlCodec.encode(logingTargetUrl);
				} while (false);

				WebUtils.setSessionAttribute(request, "logingTargetUrl", logingTargetUrl);

				response.sendRedirect(contextSite.getLoginUrl());
			}

			return false;
		}

		if (SiteUtils.simpleAuthority) {
			return true;
		}

		// 调用权限接口，验证是否有参数权限
		final UserDto loginUser = UserUtils.findForLogin();

		final Boolean isAuthority = PermissionUtils.isAuthority(loginUser.getPermissionList(), Contexts.urlBlackList.name(), permissionCode);

		if (isAuthority) {
			return true;
		}

		if (com.cardone.common.util.StringUtils.matchList(SiteUtils.jsonUrlList, url)) {
			response.setCharacterEncoding(CharEncoding.UTF_8);
			response.setContentType("application/json");

			Writer out = null;

			try {
				final ReturnDataDto<String> returnDataDto = ReturnDataUtils.newReturnData(new DictionaryException("没有权限").setTypeCode("authorityUrl").setCode("not"), SiteUtils.log);

				out = response.getWriter();

				out.write(MapperUtils.toJson(returnDataDto));
			} catch (final Exception e) {
				SiteUtils.log.error(e.getMessage(), e);
			} finally {
				IOUtils.closeQuietly(out);
			}
		} else {
			response.sendRedirect(contextSite.getNotAuthorityUrl());
		}

		return false;
	}

	/**
	 * 查询：站标识
	 *
	 * @param code
	 *          代码
	 * @return 站
	 */
	public static String readIdByCode(final String code) {
		return ContextHolder.getBean(SiteService.class).readIdByCode(code);
	}
}
